`include "defines.svh"

module StageIF (
    input   logic                       clock,
    input   logic                       reset,

    // StageID -> StageIF
    input   logic                       id2if_allow,
    input   logic [`BJ_BUS_WD-1:0]      bj_bus,

    // CSR -> StageIF
    input   logic                       csr_taken,
    input   logic [63:0]                csr_target,

    // StageIF -> StageID
    output  logic                       if2id_fired,
    output  logic [`IF2ID_BUS_WD-1:0]   if2id_bus,

    // StageID -> StageIF @ Cancel Flag
    input   logic                       id_cancel,
    // StageEX -> StageIF @ Cancel Flag
    input   logic                       ex_cancel,
    // StageMA -> StageIF @ Cancel Flag
    input   logic                       ma_cancel,
    // StageWB -> StageIF @ Cancel Flag
    input   logic                       wb_cancel,

    /* ********* InstBus Interface ********* */
    output  logic                       ibus_req,
    output  logic                       ibus_op,
    output  logic [2:0]                 ibus_size,
    output  logic [7:0]                 ibus_mask,
    output  logic [31:0]                ibus_addr,
    output  logic [63:0]                ibus_wdata,
    input   logic [63:0]                ibus_rdata,
    input   logic                       ibus_addr_ok,
    input   logic                       ibus_data_ok,
    output  logic                       ibus_uncache,
    input   logic                       ibus_cache_miss,
    input   logic                       ibus_load_fault,
    /* verilator lint_off UNUSEDSIGNAL */
    input   logic                       ibus_store_fault
    /* ********* InstBus Interface ********* */
);

    logic [63:0]    if_pc;
    logic [31:0]    if_inst;
    logic           if_valid;
    logic           if_ready;

/* ************************* Pipeline ************************* */
/* -------------- StagePF <=> StageIF -------------- */
    logic           pf_ready;
    logic           pf_valid;
    logic           pf2if_fired;
    logic           if2pf_allow;
    logic [63:0]    pc_plus_4;
    logic [63:0]    next_pc;

    assign  pf_ready = ibus_req & ibus_addr_ok;
    assign  pf_valid = 1'b1;
    assign  pf2if_fired = pf_ready & pf_valid;
    assign  pc_plus_4 = if_pc + 64'h4;
    assign  next_pc = next_pc_buf_valid ? next_pc_buf
                    : csr_taken ? csr_target
                    : bj_taken  ? bj_target
                    : pc_plus_4 ;


    assign  if_ready = (ibus_data_ok | inst_buf_valid) & (~inst_discard);
    assign  if2pf_allow = (~if_valid) | (if_ready & id2if_allow);
    assign  if2id_fired = if_valid & if_ready;

    always_ff @ (posedge clock) begin
        // 不可随意用 csr_taken 使 if_valid 失效
        if (reset) begin
            if_valid <= 1'b0;
        end
        else if (if2pf_allow | csr_taken) begin
            if_valid <= pf2if_fired;
        end
        else if (bj_taken) begin
            if_valid <= 1'b0;
        end
    end

    always_ff @ (posedge clock) begin
        if (reset) begin
            if_pc <= 64'h7FFFFFFC;
        end
        else if (pf2if_fired & if2pf_allow) begin
            if_pc <= next_pc;
        end
    end
/* -------------- StagePF <=> StageIF -------------- */

/* -------------- StageIF <=> StageID -------------- */
    assign if2id_bus = {
        // for debug
        ibus_cache_miss,
        // to StageID
        if_pc,
        if_inst,
        pc_plus_4,
        if_excp_flags
    };
/* -------------- StageIF <=> StageID -------------- */
/* ************************* Pipeline ************************* */


/* ************************* Fetch ************************* */
    assign ibus_req = (~reset)
                    & (if2pf_allow)
                    & (ibus_addr_ack & ibus_data_ok | ~ibus_addr_ack)
                    & (~bj_stall)
                    & (~cancel_fetch); // 其他流水级产生异常或者中断, 需要取消取指
    assign ibus_op    = 1'b0;
    assign ibus_size  = 3'h2;
    assign ibus_mask  = 8'b00000000;
    assign ibus_addr  = next_pc[31:0];
    assign ibus_wdata = 64'b0;
    assign ibus_uncache = ~(ibus_addr >= 32'h8000_0000 && ibus_addr < 32'h9000_0000);

    // Addr Ack
    logic  ibus_addr_ack;
    always_ff @(posedge clock) begin
        if (reset) begin
            ibus_addr_ack <= 1'b0;
        end
        else if (ibus_req & ibus_addr_ok) begin
            ibus_addr_ack <= 1'b1;
        end
        else if (ibus_addr_ack & ibus_data_ok) begin
            ibus_addr_ack <= 1'b0;
        end
    end

    // Inst Buffer
    logic [63:0]    inst_buf;
    logic           inst_buf_valid;
    always_ff @ (posedge clock) begin
        if (reset | csr_taken) begin
            inst_buf_valid <= 1'b0;
        end
        // IF 收到指令, 但是 ID 不允许其进入, 需要缓存起来
        else if (ibus_data_ok & (~id2if_allow)) begin
            inst_buf <= ibus_rdata;
            inst_buf_valid <= 1'b1;
        end
        else if (inst_buf_valid & id2if_allow) begin
            inst_buf_valid <= 1'b0;
        end
    end

    // NextPC Buffer
    // 若收到flush信号时, 不允许请求地址, 可能会导致 NextPC 丢失, 需要暂存
    logic [63:0]    next_pc_buf;
    logic           next_pc_buf_valid;
    always_ff @(posedge clock) begin
        if (reset) begin
            next_pc_buf_valid <= 1'b0;
        end
        else if (csr_taken & ~pf_ready) begin
            next_pc_buf <= csr_target;
            next_pc_buf_valid <= 1'b1;
        end
        else if (bj_taken & ~pf_ready) begin
            next_pc_buf <= bj_target;
            next_pc_buf_valid <= 1'b1;
        end
        else if (next_pc_buf_valid & pf_ready) begin
            next_pc_buf_valid <= 1'b0;
        end
    end

    // Inst Discard
    logic inst_discard;
    always_ff @ (posedge clock) begin
        if (reset) begin
            inst_discard <= 1'b0;
        end
        else if (csr_taken & (~if2pf_allow & ~if_ready)) begin
            inst_discard <= 1'b1;
        end
        else if (inst_discard & ibus_data_ok) begin
            inst_discard <= 1'b0;
        end
    end

    // Select Inst
    wire [63:0] inst_data = inst_buf_valid ? inst_buf : ibus_rdata;
    assign if_inst = if_pc[2] ? inst_data[63:32] : inst_data[31:0];
/* ************************* Fetch ************************* */


/* ************************** Trap ************************** */
    // StageIF -> StageID
    wire inst_addr_misaligned = if_valid & (|next_pc[1:0]);
    wire inst_access_fault = if_valid & ibus_load_fault;
    logic [15:0] if_excp_flags;
    assign if_excp_flags[0] = inst_addr_misaligned;
    assign if_excp_flags[1] = ~inst_addr_misaligned & inst_access_fault;
    assign if_excp_flags[15:2] = 0;

/* ----------------------------- Cancel Fetch ----------------------------- */
    wire if_cancel = if_valid & (|if_excp_flags);
    wire cancel_fetch = if_cancel
                      | id_cancel
                      | ex_cancel
                      | ma_cancel
                      | wb_cancel;
/* ----------------------------- Cancel Fetch ----------------------------- */
/* ************************** Trap ************************** */


/* ************************* Branch & Jump ************************* */
    logic           bj_taken;
    logic           bj_stall;
    logic [63:0]    bj_target;
    assign { bj_taken, bj_stall, bj_target } = bj_bus;
/* ************************* Branch & Jump ************************* */


endmodule
